package com.ywr;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;

import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import static org.apache.commons.lang3.math.NumberUtils.INTEGER_ZERO;

public class StreamUtils {
    private StreamUtils() {
    }

    public static <T> List<T> filter(List<T> data, Predicate<T> func) {
        if (Objects.isNull(data)) {
            return Collections.emptyList();
        }
        return data.stream().filter(func).collect(Collectors.toList());
    }

    public static <T> List<T> distinct(List<T> data) {
        if (Objects.isNull(data)) {
            return Collections.emptyList();
        }
        return data.stream().distinct().collect(Collectors.toList());
    }

    public static String getTheRestAfterSub(String str, String sub) {
        int index = StringUtils.indexOf(str, sub);
        if(index == -1) {
            return StringUtils.EMPTY;
        }
        int rest = index + sub.length();
        if(rest < 0 || rest >= str.length()) {
            return StringUtils.EMPTY;
        }
        return StringUtils.substring(str, rest);
    }

    public static <T> List<T> findIntersection(List<T> listA, List<T> listB) {
        if(CollectionUtils.isEmpty(listA) || CollectionUtils.isEmpty(listB)) {
            return Collections.emptyList();
        }

        List<T> result = Lists.newArrayList();
        for(T ta: listA) {
            for(T tb: listB) {
                if(Objects.equals(ta, tb)) {
                    result.add(ta);
                }
            }
        }
        return result;
    }

    public static <T, B> List<B> flatMap(Collection<List<T>> data, Function<T, B> func) {
        if (Objects.isNull(data)) {
            return Collections.emptyList();
        }
        return data.stream().flatMap(list -> list.stream().map(func)).collect(Collectors.toList());
    }

    public static <K, V, RK, RV> Map<RK, RV> mapToMap(Map<K, V> data, Function<K, RK> kFunc, Function<V, RV> vFunc) {
        if (Objects.isNull(data)) {
            return null;
        }
        return data.entrySet().stream().collect(Collectors.toMap(e -> kFunc.apply(e.getKey()), e -> vFunc.apply(e.getValue())));
    }

    public static <K, V> Map<V, List<K>> reverseKV(Map<K, List<V>> data) {
        if (Objects.isNull(data)) {
            return null;
        }

        Map<V, List<K>> reversedMap = Maps.newHashMap();
        data.forEach((k, vs) -> vs.forEach(v -> {
            if(!reversedMap.containsKey(v)) {
                reversedMap.put(v, Lists.newArrayList());
            }
            reversedMap.get(v).add(k);
        }));
        return reversedMap;
    }

    public static <T, B> List<B> map(List<T> data, Function<T, B> func) {
        if (Objects.isNull(data)) {
            return Collections.emptyList();
        }
        return data.stream().map(func).collect(Collectors.toList());
    }

    public static <T, B> List<B> map(Set<T> data, Function<T, B> func) {
        if (Objects.isNull(data)) {
            return Collections.emptyList();
        }
        return data.stream().map(func).collect(Collectors.toList());
    }

    public static <T> List<T> emptyListIfNull(List<T> list) {
        if(Objects.isNull(list)){
            return Collections.emptyList();
        }
        return list;
    }

    public static <T> T first(List<T> collections) {
        return Optional.ofNullable(collections)
                .filter(CollectionUtils::isNotEmpty)
                .map(list -> list.get(INTEGER_ZERO))
                .orElse(null);
    }

    public static <T> void peek(List<T> collections, Consumer<T> func) {
        if (CollectionUtils.isEmpty(collections)) {
            return;
        }

        collections.forEach(func);
    }

    public static <T, B> T findBy(B attr, List<T> collections, Function<T, B> func) {
        if (CollectionUtils.isEmpty(collections)) {
            return null;
        }

        return collections.stream().filter(t -> Objects.equals(attr, func.apply(t))).findFirst().orElse(null);
    }

    public static <T, B> List<T> excludeBy(B attr, List<T> collections, Function<T, B> func) {
        if (CollectionUtils.isEmpty(collections)) {
            return Collections.emptyList();
        }

        return collections.stream().filter(t -> !Objects.equals(attr, func.apply(t))).collect(Collectors.toList());
    }

    public static <A, B> List<B> to(Collection<A> a, Function<A, B> convertor) {
        return (a != null ? a : Collections.<A>emptyList()).stream().map(convertor).collect(Collectors.toList());
    }

    public static <K> boolean containsKey(Map<K, ?> map, K key) {
        return MapUtils.isNotEmpty(map) && map.containsKey(key);
    }

    public static <K> boolean notContainsKey(Map<K, ?> map, K key) {
        return !containsKey(map, key);
    }

    public static <T, K> Map<K, T> toMap(List<T> data, Function<T, K> func) {
        return toListMap(data, func).entrySet().stream()
                .collect(Collectors.toMap(Map.Entry::getKey, e -> first(e.getValue())));
    }

    public static <T, K, V> Map<K, V> toMap(List<T> data, Function<T, K> kFunc, Function<T, V> vFunc) {
        return toListMap(data, kFunc).entrySet().stream()
                .collect(Collectors.toMap(Map.Entry::getKey, e -> vFunc.apply(first(e.getValue()))));
    }

    public static <T, K, V> Map<K, List<V>> toListMap(List<T> data, Function<T, K> kFunc, Function<T, V> vFunc) {
        return toListMap(data, kFunc).entrySet().stream()
                .collect(Collectors.toMap(Map.Entry::getKey, e -> emptyListIfNull(map(e.getValue(), vFunc))));
    }

    public static <T, K> Map<K, List<T>> toListMap(List<T> data, Function<T, K> kFunc) {
        if (CollectionUtils.isEmpty(data)) {
            return Collections.emptyMap();
        }

        return data.stream().collect(Collectors.groupingBy(kFunc));
    }
}
